#ifdef _WIN32
#include <windows.h>
#endif
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>

#include <GL/glew.h>
#include "textfile.h"
#include "shader.h"
#include "light.h"

#include <vector>
#include <math.h>

#ifndef __APPLE__
#include <GL/gl.h>
#include <GL/glut.h>
#else
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#endif
#include "Matrix4x4.h"
#include <AR/gsub.h>
#include <AR/video.h>
#include <AR/param.h>
#include <AR/ar.h>

#ifdef _WIN32
#include "..\..\bin\Shaders\globals.h"
#else
#include "../../bin/Shaders/globals.h"
#endif

#ifndef _WIN32
#ifndef max
#define max(a,b)            (((a) > (b)) ? (a) : (b))
#endif

#ifndef min
#define min(a,b)            (((a) < (b)) ? (a) : (b))
#endif
#endif
//
// Camera configuration.
//
#ifdef _WIN32
char            *vconf = "Data\\WDM_camera_flipV.xml";
#else
char            *vconf = "-dev=/dev/video0";
#endif

int             xsize, ysize;
int             thresh = 100;
int             count = 0;

char           *cparam_name    = "Data/camera_para.dat";
ARParam         cparam;

char           *patt_name      = "Data/patt.hiro";
int             patt_id;
double          patt_width     = 80.0;
double          patt_center[2] = {0.0, 0.0};
double          patt_trans[3][4];
double          patt_trans_avg[3][4];
int             reseting = 1;

static void   init(void);
static void   cleanup(void);
static void   keyEvent( unsigned char key, int x, int y);
static void   mainLoop(void);
static void   draw( void );

#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define BALL_SIZE 25
#define ENVMAP_RES 128

// Handles for the shader programs
Shader *main_shader, *with_shadow_shader, *gather_shader, *ldr2hdr_shader, *object_shader, *debug_shadow_shader;
Light *test_light;
Light *env_light;

GLuint accum, accum_fbo, webcam_feed;
GLhandleARB FragmentShaderLDR2HDR;

GLuint hdr_fbo;
GLuint hdr_tex;

GLuint env_fbo;
GLuint env_tex;

// Handle for the webcam feed texture
GLuint webcam_texture;
int webcam_sampler;
int sx=-4, sy=80, sz=37, sr=22;

// just in case
#ifndef M_PI
#define M_PI           3.14159265358979323846
#endif


void checkGLErrors()
{
	GLenum err = glGetError();
    if (err!=GL_NO_ERROR)
	{
		switch(err)
		{
			case 0x0500: 
				printf("ERROR - GL_INVALID_ENUM\n"); 
				break;
			case 0x0501: 
				printf("ERROR - GL_INVALID_VALUE\n"); 
				break;     
			case 0x0502: 
				printf("ERROR - GL_INVALID_OPERATION\n"); 
				break; 
			case 0x0503: 
				printf("ERROR - GL_STACK_OVERFLOW\n"); 
				break;    
			case 0x0504: 
				printf("ERROR - GL_STACK_UNDERFLOW\n"); 
				break;   
			case 0x0505: 
				printf("ERROR - GL_OUT_OF_MEMORY\n"); 
				break;     
		};
	}
}

static void updateMovingAvg()
{
	int i, j;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			if (reseting)
			{
				patt_trans_avg[i][j] = patt_trans[i][j];
			}
			else
			{
				patt_trans_avg[i][j] = patt_trans_avg[i][j] * 0.75 + patt_trans[i][j] * 0.25;
			}
		}
	}
}


// Loads a portable pixel map file
void loadPPM(const char *file_name, int &w, int &h, std::vector<unsigned char> &pixels)
{
  FILE *FilePointer;

  FilePointer = fopen(file_name, "rb");
    // Assumes 8bit per channel
    fscanf(FilePointer, "P6\n");
    fscanf(FilePointer, "%d %d\n", &w, &h);
    fscanf(FilePointer, "255\n");

    pixels.resize(w * h * 3);

    size_t NumReadData = fread(&pixels[0], 1, w * h * 3, FilePointer);
    if (NumReadData != pixels.size())
    {
      return;
    }
  fclose(FilePointer);
}

// Performs LDR2HDR and panoramic transformation
// (FragmentShaderLDR2HDR should be created and compiled beforehand)
void convertLDR2HDR(const int input_texture_index, const float gamma, const float exposure, const float color_correction[3], const int resolution, const GLuint HDR_FBO)
{
  // < Parameters >
  // input_texture_index: GL_TEXTURE[NUM] of the input texture
  // gamma: gamma value for gamma correction
  // exposure: exposure value of the input
  // color_correction: scaling factor for the final output
  // resolution: resolution of the input and output (assumes a square texture)
  // HDR_FBO: FBO of the output texture


  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, HDR_FBO);
  glViewport(0,0,ENVMAP_RES,ENVMAP_RES);
  //glUseProgram(FragmentShaderLDR2HDR);
  ldr2hdr_shader->use();


  // Rrepares the constant used in the shader
  float EnvBufInfo[4];
  EnvBufInfo[0] = float(resolution);
  EnvBufInfo[1] = float(resolution);
  EnvBufInfo[2] = 1.0f / float(resolution);
  EnvBufInfo[3] = 1.0f / float(resolution);

  GLhandleARB FragmentShaderLDR2HDR = ldr2hdr_shader->getHandle();
  //ldr2hdr_shader->setUniform1i("input_tex", input_texture_index - GL_TEXTURE0);
  glUniform4fv(glGetUniformLocation(FragmentShaderLDR2HDR, "BufInfo"), 1, EnvBufInfo);
  glUniform1i(glGetUniformLocation(FragmentShaderLDR2HDR, "input_tex"), input_texture_index - GL_TEXTURE0);
  glUniform1f(glGetUniformLocation(FragmentShaderLDR2HDR, "Gamma"), gamma);
  glUniform1f(glGetUniformLocation(FragmentShaderLDR2HDR, "Exposure"), exposure);
  glUniform3fv(glGetUniformLocation(FragmentShaderLDR2HDR, "ColorCorrectionFactor"), 1, color_correction);

  // Runs the shader
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glRecti(1, 1, -1, -1);
  glPopMatrix();
  // This is a bad manner: assumes that the frame buffer was attached
  //glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}


// to-do: use Summed Area Table
void MedianSampling(const int resolution, const int max_level, std::vector<float> ModEnvMap, std::vector<float> &lights_dir, std::vector<float> &lights_col, const int x0, const int y0, const int x1, const int y1, const int level = 0, const int index = 1)
{
  int w = x1 - x0;
  int h = y1 - y0;
  float cs;
  {
    float fy = ((y0 + y1) * 0.5f + 0.5f) / float(resolution);
    float theta = fy * M_PI;
    //cs = 1.0f;
	cs = fabs(cos(fabs(theta - M_PI * 0.5f)));

  }

  // generate a clustered light source
  if (level == max_level)
  {
    // set the direction of the light source toward the center of the region
    int offset = 1 << max_level;

    float fx = ((x0 + x1) * 0.5f + 0.5f) / float(resolution);
    float fy = ((y0 + y1) * 0.5f + 0.5f) / float(resolution);
    float theta = fy * M_PI;
//    float phi = fx * 2.0f * M_PI;
    float phi = (fx - 0.25) * 2.0f * M_PI;

    lights_dir[3 * (index - offset) + 0] = -sin(theta) * cos(phi);
    lights_dir[3 * (index - offset) + 1] = -cos(theta);
    lights_dir[3 * (index - offset) + 2] = -sin(theta) * sin(phi);

    // compute the average light source color
    float er = 0.0f;
    float eg = 0.0f;
    float eb = 0.0f;
    int ecount = 1;
    for (int i = x0; i < x1; i++)
    {
      for (int j = y0; j < y1; j++)
      {
        int k = (i + j * resolution) * 4;
        er += ModEnvMap[k + 0];
        eg += ModEnvMap[k + 1];
        eb += ModEnvMap[k + 2];
        ecount++;
      }
    }
    lights_col[3 * (index - offset) + 0] = er / float(ecount) * offset;
    lights_col[3 * (index - offset) + 1] = eg / float(ecount) * offset;
    lights_col[3 * (index - offset) + 2] = eb / float(ecount) * offset;
 }
  else
  {
    if (w  >= (h * cs))
    {
      // precalcuation
      std::vector<float> e(w);
      float e_sum = 0.0f;
      for (int i = 0; i < w; i++)
      {
        e[i] = 0.0;
        for (int j = y0; j < y1; j++)
        {
          int k = ((i + x0) + j * resolution) * 4;
          e[i] += (0.2125f * ModEnvMap[k + 0] + 0.7154f * ModEnvMap[k + 1] + 0.0721f * ModEnvMap[k + 2]) + 1e-20f;
        }
        e_sum += e[i];
      }

      // find the median
      e_sum /= 2.0f;
      int median = x0;
      float e_accum = 0.0f;
      for (int i = 1; i < w - 1; i++)
      {
        median = i + x0;
        e_accum += e[i];
        if (e_accum >= e_sum)
        {
          break;
        }
      }

      MedianSampling(resolution, max_level, ModEnvMap, lights_dir, lights_col,        x0, y0, median, y1, level + 1, index * 2 + 0);
      MedianSampling(resolution, max_level, ModEnvMap, lights_dir, lights_col,median + 1, y0,     x1, y1, level + 1, index * 2 + 1);
    }
    else
    {
      // precalcuation
      std::vector<float> e(h);
      float e_sum = 0.0f;
      for (int j = 0; j < h; j++)
      {
        e[j] = 0.0;
        for (int i = x0; i < x1; i++)
        {
          int k = (i + (j + y0) * resolution) * 4;
          e[j] += (0.2125f * ModEnvMap[k + 0] + 0.7154 * ModEnvMap[k + 1] + 0.0721 * ModEnvMap[k + 2]) + 1e-20f;
        }
        e_sum += e[j];
      }

      // find the median
      e_sum /= 2.0f;
      int median = y0;
      float e_accum = 0.0f;
      for (int j = 1; j < h - 1; j++)
      {
        median = j + y0;
        e_accum += e[j];
        if (e_accum >= e_sum)
        {
          break;
        }
      }

      MedianSampling(resolution, max_level, ModEnvMap, lights_dir, lights_col, x0,         y0, x1, median, level + 1, index * 2 + 0);
      MedianSampling(resolution, max_level, ModEnvMap, lights_dir, lights_col, x0, median + 1, x1,     y1, level + 1, index * 2 + 1);
    }
  }
}











// Generates light sources from the output of converLDR2HDR
void generateLightsMedian(const GLuint HDR_FBO, const int resolution, const int max_level, std::vector<float> &lights_dir, std::vector<float> &lights_col)
{
  // < Parameters >
  // HDR_FBO: FBO of the input texture (i.e., the output of converLDR2HDR in RGBA)
  // resolution: resolution of the input (assumes a square texture)
  // num: the number of light sources to be generated
  // lights_dir: the output array for light source directions {x0, y0, z0, x1, y1, ...}
  // lights_col: the output array for light source colors (radiant intensities) {r0, g0, b0, r1, g1, ...}

  // Reads back the texture to the main memory
  std::vector<float> EnvMap(resolution * resolution * 4);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, HDR_FBO);
  glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
  glReadPixels(0, 0, resolution, resolution, GL_RGBA, GL_FLOAT, &EnvMap[0]);

  // Allocates a temporary array
  std::vector<float> ModEnvMap(resolution * resolution * 4);
  int EnvMapW = resolution;
  int EnvMapH = resolution;

  // Constructs the cummulative distribution function
  ModEnvMap[0] = 0.0f;
  float EnvAccum = 0.0f;
//#pragma omp parallel for shared(ModEnvMap)
  for (int j = 0; j < EnvMapH; j++)
  {
    for (int i = 0; i < EnvMapW; i++)
    {
      unsigned int k = (i + j * EnvMapW) * 4;

      float y = (j + 0.5f) / float(EnvMapH);
      float theta = y * M_PI;

      // Applies the angular scaling due to the parameterization
      float t = fabs(cos(fabs(theta - M_PI * 0.5f)));
      ModEnvMap[k + 0] = EnvMap[k + 0] * t;
      ModEnvMap[k + 1] = EnvMap[k + 1] * t;
      ModEnvMap[k + 2] = EnvMap[k + 2] * t;
    }
  }


  lights_dir.resize((1 << max_level) * 3);
  lights_col.resize((1 << max_level) * 3);
  MedianSampling(resolution, max_level, ModEnvMap, lights_dir, lights_col, 0, 0, resolution, resolution);
} 








// Generates light sources from the output of converLDR2HDR
void generateLights(const GLuint HDR_FBO, const int resolution, const int num, std::vector<float> &lights_dir, std::vector<float> &lights_col)
{
  // < Parameters >
  // HDR_FBO: FBO of the input texture (i.e., the output of converLDR2HDR in RGBA)
  // resolution: resolution of the input (assumes a square texture)
  // num: the number of light sources to be generated
  // lights_dir: the output array for light source directions {x0, y0, z0, x1, y1, ...}
  // lights_col: the output array for light source colors (radiant intensities) {r0, g0, b0, r1, g1, ...}
  
  // Allocates a temporary array
  std::vector<float> EnvMap(resolution * resolution * 4);
  std::vector<float> ModEnvMap(resolution * resolution * 4);
  
  // Reads back the texture to the main memory
  //glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, HDR_FBO);
  glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
  
  glReadPixels(0, 0, resolution, resolution, GL_RGBA, GL_FLOAT, &EnvMap[0]);
	
  int EnvMapW = resolution;
  int EnvMapH = resolution;

  // Constructs the cummulative distribution function
  ModEnvMap[0] = 0.0f;
  float EnvAccum = 0.0f;
  for (int j = 0; j < EnvMapH; j++)
  {
    for (int i = 0; i < EnvMapW; i++)
    {
      unsigned int k = (i + j * EnvMapW) * 4;

      float y = (j + 0.5f) / float(EnvMapH);
      float theta = y * M_PI;

      // Applies the angular scaling due to the parameterization
      float t = fabs(cos(fabs(theta - M_PI * 0.5f)));
      ModEnvMap[k + 0] = EnvMap[k + 0] * t;//t;//
      ModEnvMap[k + 1] = EnvMap[k + 1] * t;//t;//
      ModEnvMap[k + 2] = EnvMap[k + 2] * t;//t;//

      // Constructs the CDF
      float p = max(max(ModEnvMap[k + 0], ModEnvMap[k + 1]), ModEnvMap[k + 2]);
      EnvAccum += p;
      ModEnvMap[k + 3] = EnvAccum;
    }
  }

  // the normalization factor 
  float EnvIntegral = EnvAccum / float(EnvMapW * EnvMapH);

  // pre-scaling by the probability density function
  for (int j = 0; j < EnvMapH; j++)
  {
    for (int i = 0; i < EnvMapW; i++)
    {
      unsigned int k = (i + j * EnvMapW) * 4;

      ModEnvMap[k + 3] /= EnvAccum;

      float p = max(max(ModEnvMap[k + 0], ModEnvMap[k + 1]), ModEnvMap[k + 2]);
      float temp = EnvIntegral / (p + 1e-20f);

      ModEnvMap[k + 0] *= temp;
      ModEnvMap[k + 1] *= temp;
      ModEnvMap[k + 2] *= temp;
    }
  }

  // Performs importance sampling
  lights_dir.resize(num * 3);
  lights_col.resize(num * 3);
  unsigned int EnvMapIndex = 0;

  for (int i = 0; i < num; i++)
  {
    // Inverts the CDF
    float light_col_average_r = 0.0f;
    float light_col_average_g = 0.0f;
    float light_col_average_b = 0.0f;

    float light_pos_average_x = 0.0f;
    float light_pos_average_y = 0.0f;
    float light_pos_average_z = 0.0f;

    unsigned int light_col_count = 0;

    float t = (i + 0.5f) / float(num);
    do
    {
      EnvMapIndex++;
      light_col_count++;

      light_col_average_r += ModEnvMap[4 * EnvMapIndex + 0];
      light_col_average_g += ModEnvMap[4 * EnvMapIndex + 1];
      light_col_average_b += ModEnvMap[4 * EnvMapIndex + 2];

      int x = EnvMapIndex % EnvMapW;
      int y = EnvMapIndex / EnvMapW;

      float fx = (x + 0.5f) / float(EnvMapW);
      float fy = (y + 0.5f) / float(EnvMapH);

      float theta = fy * M_PI;
      float phi = fx * 2.0f * M_PI;

      light_pos_average_x += sin(theta) * cos(phi);
      light_pos_average_y += cos(theta);
      light_pos_average_z += sin(theta) * sin(phi);
    }
    while (ModEnvMap[4 * EnvMapIndex + 3] <= t);

    //
    lights_col[3 * i + 0] = ModEnvMap[4 * EnvMapIndex + 0];//light_col_average_r / float(light_col_count);//
    lights_col[3 * i + 1] = ModEnvMap[4 * EnvMapIndex + 1];//light_col_average_g / float(light_col_count);//
    lights_col[3 * i + 2] = ModEnvMap[4 * EnvMapIndex + 2];//light_col_average_b / float(light_col_count);//

    
    // Computes the light source direction (pointing toward the light source)
    float x = EnvMapIndex % EnvMapW;//light_pos_average_x / float(light_col_count);//
    float y = EnvMapIndex / EnvMapW;//light_pos_average_y / float(light_col_count);//

    float fx = (x + 0.5f) / float(EnvMapW);
    float fy = (y + 0.5f) / float(EnvMapH);

    float theta = fy * M_PI; //acosf(2.0f * rand() / float(RAND_MAX)- 1.0f);//
    float phi = (fx - 0.25) * 2.0f * M_PI; //rand() / float(RAND_MAX) * 2.0f * M_PI;//
    

    float l = sqrtf(light_pos_average_x * light_pos_average_x + light_pos_average_y * light_pos_average_y + light_pos_average_z * light_pos_average_z);
    light_pos_average_x /= l;
    light_pos_average_y /= l;
    light_pos_average_z /= l;


	// at (x,y) == (0.5,0.5) light dir should point back at camera - <x,y,z> = <0,0,-1>
	// This is actually the normal on the sphere
    lights_dir[3 * i + 0] = -sin(theta) * cos(phi); //
    lights_dir[3 * i + 1] = -cos(theta); //
    lights_dir[3 * i + 2] = -sin(theta) * sin(phi); //

    /*
    lights_dir[3 * i + 0] =  light_pos_average_x;//sin(theta) * cos(phi); //
    lights_dir[3 * i + 1] =  light_pos_average_y;// cos(theta); //
    lights_dir[3 * i + 2] =  light_pos_average_z;//sin(theta) * sin(phi); //
    */
  }
} 


void printSphereValues()
{
    printf("sx = %d, sy = %d, sz = %d, sr = %d", sx, sy, sz, sr);
}


void printInfoLog(GLhandleARB obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;
    
    glGetObjectParameterivARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB,
                         &infologLength);
    
    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetInfoLogARB(obj, infologLength, &charsWritten, infoLog); 
        printf("%s\n",infoLog);
        free(infoLog);
    }
}



void initShaders() {
     main_shader = new Shader("Shaders/vert.glsl", "Shaders/frag.glsl");
	 gather_shader = new Shader("Shaders/gather.vert", "Shaders/gather.frag");
	 with_shadow_shader = new Shader("Shaders/with_shadow.vert", "Shaders/with_shadow.frag");
     debug_shadow_shader = new Shader("Shaders/debug_shadow.vert", "Shaders/debug_shadow.frag");
     ldr2hdr_shader = new Shader("Shaders/LDR2HDR.frag");
	 object_shader = new Shader("Shaders/object.vert", "Shaders/object.frag");
}

void checkFBOCompleteness()
{
    // check FBO status
	GLenum FBOstatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	if(FBOstatus != GL_FRAMEBUFFER_COMPLETE_EXT)
	{
		printf("GL_FRAMEBUFFER_COMPLETE_EXT failed, CANNOT use FBO\n");
		switch(FBOstatus)
		{
		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
			printf("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT\n");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
			printf("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT\n");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
			printf("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT\n");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
			printf("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT\n");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
			printf("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT\n");
			break;
		case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
			printf("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT\n");
		}
	}
}

void createAccumulationFBO()
{
	//checkGLErrors();
	//Create the accumulated floating point buffer
	glGenTextures(1, &accum);
	glBindTexture(GL_TEXTURE_2D, accum);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1024, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);	
	//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, 1024, 512, 0, GL_RGBA, GL_FLOAT, NULL);	
	
	glGenFramebuffersEXT(1, &accum_fbo);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, accum_fbo);
	
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_2D, accum, 0);	
	
	GLuint depthbuffer;
	glGenRenderbuffersEXT(1, &depthbuffer);
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);
	//glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, SCREEN_WIDTH, SCREEN_HEIGHT);
	glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, 1024, 512);
	glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthbuffer);
	
	//glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, 0);
	
    checkFBOCompleteness();
    
	// switch back to window-system-provided framebuffer
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    
    
	// Try to use a texture depth component
	glGenTextures(1, &hdr_tex);
	glBindTexture(GL_TEXTURE_2D, hdr_tex);
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, ENVMAP_RES, ENVMAP_RES, 0, GL_RGBA, GL_FLOAT, NULL);
	glBindTexture(GL_TEXTURE_2D, 0);
	
	// create a framebuffer object
	glGenFramebuffersEXT(1, &hdr_fbo);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, hdr_fbo);
	
	// Instruct openGL that we won't bind a color texture with the currently binded FBO

	// attach the texture to FBO depth attachment point
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_2D, hdr_tex, 0);
	
	checkFBOCompleteness();
    
    
    // Try to use a texture depth component
	glGenTextures(1, &env_tex);
	glBindTexture(GL_TEXTURE_2D, env_tex);
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, ENVMAP_RES, ENVMAP_RES, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	glBindTexture(GL_TEXTURE_2D, 0);
	glGenFramebuffersEXT(1, &env_fbo);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, env_fbo);
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_2D, env_tex, 0);
	
	checkFBOCompleteness();
}

int main(int argc, char **argv)
{
	glutInit(&argc, argv);

	init();

	glewInit();
	if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader)
		printf("Ready for GLSL\n");
	else {
		printf("No GLSL support\n");
		exit(1);
	}
    
    createAccumulationFBO();
    test_light = new Light(100, 100, 100, 0, 0, 0);
    env_light = new Light(100, 100, 100, 0, 0, 0);
	initShaders();

	arVideoCapStart();
	argMainLoop( NULL, keyEvent, mainLoop );
	return (0);
}

static void   keyEvent( unsigned char key, int x, int y)
{
    /* quit if the ESC key is pressed */
    if( key == 0x1b ) {
        printf("*** %f (frame/sec)\n", (double)count/arUtilTimer());
        cleanup();
        exit(0);
    }
    switch(key)
    {
    case 'a':
        sx-=1.0;
        break;
    case 'd':
        sx+=1.0;
        break;
    case 'w':
        sy+=1.0;
        break;
    case 's':
        sy-=1.0;
        break;
    case 'r':
        sz+=1.0;
        break;
    case 'f':
        sz-=1.0;
        break;
    case 't':
        sr+=1.0;
        break;
    case 'g':
        sr-=1.0;
        break;
    case 'p':
        printSphereValues();
    }
}

ARUint8         *dataPtr;

/* main loop */
static void mainLoop(void)
{
   // ARUint8         *dataPtr;
    ARMarkerInfo    *marker_info;
    int             marker_num;
    int             j, k;

    /* grab a vide frame */

    if( (dataPtr = (ARUint8 *)arVideoGetImage()) == NULL ) {
        arUtilSleep(2);
        return;
    }

    if( count == 0 ) arUtilTimerReset();
    count++;

    glUseProgramObjectARB(0);
    argDrawMode2D();
    argDispImage( dataPtr, 0,0 );
    
    glEnable(GL_TEXTURE_2D);

    /* detect the markers in the video frame */
    if( arDetectMarker(dataPtr, thresh, &marker_info, &marker_num) < 0 ) {
        cleanup();
        exit(0);
    }

    arVideoCapNext();

    /* check for object visibility */
    k = -1;
    for( j = 0; j < marker_num; j++ ) {
        if( patt_id == marker_info[j].id ) {
            if( k == -1 ) k = j;
            else if( marker_info[k].cf < marker_info[j].cf ) k = j;
        }
    }
    if( k == -1 ) {
        argSwapBuffers();
        return;
    }

    /* get the transformation between the marker and the real camera */
    arGetTransMat(&marker_info[k], patt_center, patt_width, patt_trans);
    updateMovingAvg();

    /* find the range */
    reseting = 0;

    draw();

    argSwapBuffers();
}

static void init( void )
{
    ARParam  wparam;
    
    /* open the video path */
    if( arVideoOpen( vconf ) < 0 ) exit(0);
    /* find the size of the window */
    if( arVideoInqSize(&xsize, &ysize) < 0 ) exit(0);
    printf("Image size (x,y) = (%d,%d)\n", xsize, ysize);

    /* set the initial camera parameters */
    if( arParamLoad(cparam_name, 1, &wparam) < 0 ) {
        printf("Camera parameter load error !!\n");
        exit(0);
    }
    arParamChangeSize( &wparam, xsize, ysize, &cparam );
    arInitCparam( &cparam );
    printf("*** Camera Parameter ***\n");
    arParamDisp( &cparam );

    if( (patt_id=arLoadPatt(patt_name)) < 0 ) {
        printf("pattern load error !!\n");
        exit(0);
    }

    /* open the graphics window */
    argInit( &cparam, 1.0, 0, 0, 0, 0 );

    //Create the webcam_texture
    glEnable(GL_TEXTURE_2D);
    glGenTextures(1, &webcam_texture);
    glBindTexture(GL_TEXTURE_2D, webcam_texture);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexImage2D(GL_TEXTURE_2D, 0, 3, ENVMAP_RES, ENVMAP_RES, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
    
    glGenTextures(1, &webcam_feed);
    glBindTexture(GL_TEXTURE_2D, webcam_feed);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexImage2D(GL_TEXTURE_2D, 0, 3, SCREEN_WIDTH, SCREEN_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);

	glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
}

/* cleanup function called when program exits */
static void cleanup(void)
{
    //Delete webcam texture
    glDeleteTextures(1, &webcam_texture);
    arVideoCapStop();
    arVideoClose();
    argCleanup();
}

void drawFloor()
{
	glColor4f(0.8f,0.65f,0.65f,0);
	glBegin(GL_QUADS);
		glNormal3f(0,0,1);
		glVertex3f(1000, 1000, -25);
		glNormal3f(0,0,1);
		glVertex3f(1000, -1000, -25);
		glNormal3f(0,0,1);
		glVertex3f(-1000, -1000, -25);
		glNormal3f(0,0,1);
		glVertex3f(-1000, 1000, -25);
	glEnd();
}
 
void drawObjectsWithoutFloor()
{
	glPushMatrix();

	//glTranslatef(0,0,25);
	//glRotatef(90,1,0,0);
	glColor4f(1,1,1,1);
	glutSolidSphere(25,20,20);
	//glutSolidTeapot(50.0);
	//glutSolidTorus(10.0, 30.0, 10, 10);
	//glutSolidCube(50.0);
	glPopMatrix();
  //drawFloor();
	
}

void drawObjects()
{
	glPushMatrix();
	//glTranslatef(0,0,25);
	//glRotatef(90,1,0,0);
	glColor4f(1,1,1,1);
	//glutSolidTeapot(50.0);
	glutSolidSphere(25,20,20);
	//glutSolidCube(50.0);
	//glutSolidTorus(10.0, 40.0, 20, 20);
	glPopMatrix();
	drawFloor();
	
}

void drawScreenQuad(int x, int y, int w, int h)
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0);
	glMatrixMode(GL_MODELVIEW);

	glDisable(GL_DEPTH_TEST);
	glPushMatrix();
	glLoadIdentity();
	glBegin(GL_QUADS);
		glTexCoord2f(0,1);
		glVertex2f(x, y);
		glTexCoord2f(0,0);
		glVertex2f(x, y+h);
		glTexCoord2f(1,0);
		glVertex2f(x+w, y+h);
		glTexCoord2f(1,1);
		glVertex2f(x+w, y);
	glEnd();
	glPopMatrix();
	
	glEnable(GL_DEPTH_TEST);
	
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}

void drawScreenQuadTextured(GLuint tex, int x, int y, int w, int h)
{
    main_shader->use();
    main_shader->setUniform1i("webcam", 0);
    //glUseProgram(0);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    drawScreenQuad(x,y,w,h);
}

void drawFinalGather()
{    
	gather_shader->use();
	main_shader->setUniform1i("webcam", 0);
    glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, accum);
	drawScreenQuad(5, 5, 200, 150);
}

void drawFinalGather(int nlights)
{
  gather_shader->use();
  main_shader->setUniform1i("webcam", 0);
  gather_shader->setUniform1i("num_lights", nlights);
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, accum);
  drawScreenQuad(5, 310, 300, 150);
 //   glEnable(GL_BLEND);
 //   glBlendFunc(GL_ONE, GL_ONE);
    //glBlendEquation(GL_FUNC_SUBTRACT);
    //drawScreenQuadTextured(accum, 5, 5, 200, 150);
  glDisable(GL_BLEND);
}

static void draw( void )
{
     double    gl_para[16];

    
     // Setup cam matrices
     argDrawMode3D();
     double *projPtr = argDraw3dCamera( 0, 0 );

    Matrix4x4 projectionMatrix(projPtr[0],projPtr[4],projPtr[8],projPtr[12],
					       projPtr[1],projPtr[5],projPtr[9],projPtr[13],
					       projPtr[2],projPtr[6],projPtr[10],projPtr[14],
					       projPtr[3],projPtr[7],projPtr[11],projPtr[15]);
     /* load the camera transformation matrix */
     argConvGlpara(patt_trans_avg, gl_para);
     Matrix4x4 viewMatrix(gl_para[0],gl_para[4],gl_para[8],gl_para[12],
				      gl_para[1],gl_para[5],gl_para[9],gl_para[13],
				      gl_para[2],gl_para[6],gl_para[10],gl_para[14],
				      gl_para[3],gl_para[7],gl_para[11],gl_para[15]);

    
     Matrix4x4 invViewMatrix(viewMatrix);
	 invViewMatrix.transpose();
     //============Read back frame buffer env map======================
     //get cam vectors for billboard computation
     GLfloat right[3], up[3];
     {
          right[0] = gl_para[0];
          right[1] = gl_para[4];
          right[2] = gl_para[8];

          up[0] = gl_para[1];
          up[1] = gl_para[5];
          up[2] = gl_para[9];
     }

     // sphere to center translation offset - world space
     static float tx = -1;
     static float ty = 76;
     static float tz = 37;

     Vector4 c1(right[0] + up[0], right[1] + up[1], right[2] + up[2],1);
     Vector4 c2(right[0] - up[0], right[1] - up[1], right[2] - up[2],1);
     Vector4 c3(-right[0] - up[0], -right[1] - up[1], -right[2] - up[2],1);
     Vector4 c4(-right[0] + up[0], -right[1] + up[1], -right[2] + up[2],1);
     c1 = c1 * Vector3(BALL_SIZE) + Vector4(tx, ty, tz,0);
     c2 = c2 * Vector3(BALL_SIZE) + Vector4(tx, ty, tz,0);
     c3 = c3 * Vector3(BALL_SIZE) + Vector4(tx, ty, tz,0);
     c4 = c4 * Vector3(BALL_SIZE) + Vector4(tx, ty, tz,0);

     c1 = (viewMatrix * c1);
     c2 = (viewMatrix * c2);
     c3 = (viewMatrix * c3);
     c4 = (viewMatrix * c4);

     Vector4 uv[4];
     uv[0] = projectionMatrix * (c1);
     uv[1] = projectionMatrix * (c2);
     uv[2] = projectionMatrix * (c3);
     uv[3] = projectionMatrix * (c4);
     uv[0] /= uv[0].w;
     uv[1] /= uv[1].w;
     uv[2] /= uv[2].w;
     uv[3] /= uv[3].w;

     float minx = 999, miny = 999;
     float maxx = -999, maxy = -999;
     for (int ix = 0; ix < 4; ++ix)
     {
          //uv[ix] = uv[ix]/uv[ix].w;
          minx = (minx > uv[ix].x) ? uv[ix].x : minx;
          maxx = (maxx < uv[ix].x) ? uv[ix].x : maxx;
          miny = (miny > uv[ix].y) ? uv[ix].y : miny;
          maxy = (maxy < uv[ix].y) ? uv[ix].y : maxy;
     }

     minx = minx * 0.5 + 0.5;
     miny = miny * 0.5 + 0.5;
     maxx = maxx * 0.5 + 0.5;
     maxy = maxy * 0.5 + 0.5;
     minx *= SCREEN_WIDTH;
     miny *= SCREEN_HEIGHT;
     maxx *= SCREEN_WIDTH;
     maxy *= SCREEN_HEIGHT;

     float offx = min(SCREEN_WIDTH-ENVMAP_RES, minx);
     float offy = min(SCREEN_HEIGHT-ENVMAP_RES, miny);

     // Read back env map

     glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
	 std::vector<unsigned char> EnvMap(ENVMAP_RES * ENVMAP_RES * 4);
     glReadPixels(offx, offy, ENVMAP_RES, ENVMAP_RES, GL_RGBA, GL_UNSIGNED_BYTE, &EnvMap[0]);

     glBindTexture(GL_TEXTURE_2D, webcam_texture);
     glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ENVMAP_RES, ENVMAP_RES, GL_RGBA, GL_UNSIGNED_BYTE, &EnvMap[0]);
     //================================================================

     // render env map
	 {
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,env_fbo);
		glViewport(0,0,ENVMAP_RES,ENVMAP_RES);
		glClear(GL_COLOR_BUFFER_BIT);
		main_shader->use();
		main_shader->setUniform1i("webcam", 0);
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, webcam_texture);

		// Packed UVs minx miny maxx maxy
		Vector4 px_uv((minx - offx)/float(ENVMAP_RES), (miny - offy)/float(ENVMAP_RES), (maxx-minx)/float(ENVMAP_RES), (maxy-miny)/float(ENVMAP_RES));
		glEnable( GL_TEXTURE_2D );
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		         
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		gluOrtho2D(0, ENVMAP_RES, ENVMAP_RES, 0);
		glMatrixMode(GL_MODELVIEW);

		glBegin( GL_QUADS );
		glTexCoord2d( px_uv.z, px_uv.w);
		glVertex2d(0,0);
		glTexCoord2d( px_uv.z, px_uv.y);
		glVertex2d(0,ENVMAP_RES);
		glTexCoord2d( px_uv.x, px_uv.y);
		glVertex2d(ENVMAP_RES,ENVMAP_RES);
		glTexCoord2d( px_uv.x, px_uv.w);
		glVertex2d(ENVMAP_RES,0);
		glEnd();

		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glMatrixMode(GL_MODELVIEW);
	 }
     
     glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0);
     glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
     
     
     
     
    
    glBindTexture(GL_TEXTURE_2D, env_tex);
    const float color_correction[3] = {800.0f, 800.0f, 800.0f};
    convertLDR2HDR(GL_TEXTURE0, 2.2f, 4.0f, color_correction, ENVMAP_RES, hdr_fbo);


    std::vector<float> lights_dir;
    std::vector<float> lights_col;
	//generateLightsMedian(hdr_fbo, ENVMAP_RES, 7, lights_dir, lights_col);
    generateLights(hdr_fbo, ENVMAP_RES, 128, lights_dir, lights_col);
#if 1
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, accum_fbo);
	glViewport(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     
	glDisable(GL_BLEND);
	glDisable(GL_ALPHA_TEST);
	glBlendFunc(GL_ONE, GL_ONE);
	glBlendEquation(GL_FUNC_ADD);	

    glDepthMask(GL_TRUE);
    for(unsigned int i=0; i<lights_dir.size(); i+=3)
    { 
#if 1
		Vector4 c1(lights_dir[i], lights_dir[i+1], lights_dir[i+2], 0);
		c1 = (invViewMatrix * c1);
#else
     //Vector4 c1(1, -1, 1,0);
#endif

#define DIM_HACK 0.25f
		c1.w = 0.0f;
		c1.normalize();
		c1.w = 0.0f;
		c1 = c1 * 250.f;

		env_light->setPosition(c1.x, c1.y, c1.z);
		env_light->beginDraw();
		drawObjectsWithoutFloor();
		env_light->endDraw();


		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, accum_fbo);
		glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
		if (i == 3)
		{
			glEnable(GL_BLEND);
			glDepthFunc(GL_LEQUAL);
			glDepthMask(GL_FALSE);
		}

         with_shadow_shader->use();
         with_shadow_shader->setUniform1i("ShadowMap", 0);
		 glActiveTexture(GL_TEXTURE0);
		 glBindTexture(GL_TEXTURE_2D, env_light->depth);

         //with_shadow_shader->setUniform3f("lpos", -lights_dir[i], -lights_dir[i+2], lights_dir[i+1]);
         with_shadow_shader->setUniform3f("lpos", c1.x, c1.y, c1.z);
		 with_shadow_shader->setUniform3f("lightDir", lights_dir[i], lights_dir[i+1], lights_dir[i+2]);
         with_shadow_shader->setUniform3f("lightCol", lights_col[i]*DIM_HACK, lights_col[i+1]*DIM_HACK, lights_col[i+2]*DIM_HACK);
         glUniformMatrix4fv(glGetUniformLocation(with_shadow_shader->getHandle(), "viewMatrix"), 1, true, &invViewMatrix.m11);
         glMatrixMode(GL_MODELVIEW);
         //glPushMatrix();
         glLoadIdentity();
         glLoadMatrixd( gl_para );
         env_light->use(0);
         drawObjects();
    }
    
#endif

     
     glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
     glViewport(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
     
	 glDepthMask(GL_TRUE);
	 glEnable(GL_DEPTH_TEST);
     glDepthFunc(GL_LEQUAL);
     glClearDepth( 1.0 );
     glClear(GL_DEPTH_BUFFER_BIT);




    //
#if 1
    gather_shader->use();
    gather_shader->setUniform1i("Texture", 0);
    gather_shader->setUniform1i("num_lights", (int)(lights_dir.size()/3.0));
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, accum);
    //glEnable(GL_BLEND);
	glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
	glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_ONE);
	drawScreenQuad(0, SCREEN_HEIGHT-512, 1024, 512);
	glBlendEquation(GL_FUNC_ADD);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	drawScreenQuad(0, SCREEN_HEIGHT-512, 1024, 512);
#endif

#if 0
	glDisable(GL_BLEND);
    object_shader->use();
	object_shader->setUniform3fv("lightDir", LIGHT_COUNT*3, &lights_dir[0]);
	object_shader->setUniform3fv("lightCol", LIGHT_COUNT*3, &lights_col[0]);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glLoadMatrixd( gl_para );
    drawObjectsWithoutFloor();
	
	glLoadIdentity();
	glTranslatef(gl_para[12], gl_para[13], gl_para[14]);
	for(unsigned int i=0; i<lights_dir.size(); i+=3)
	{
		Vector4 d1(lights_dir[i], lights_dir[i+1], lights_dir[i+2],0);
		d1 *= 60;
		
		glBegin(GL_LINES);
		   glColor3fv(&lights_col[i]);
		   glVertex3f(0, 0, 0);
		   glVertex3fv(&d1.x);
		glEnd();
	}
#endif
	glDisable(GL_BLEND);
    drawScreenQuadTextured(env_tex, 5, 155, 150, 150);
    drawScreenQuadTextured(hdr_tex, 5, 5, 300, 150);
    //drawScreenQuadTextured(env_light->depth, 5, 155, 150, 150);

    drawFinalGather((int)(lights_dir.size()/3.0));
    //drawDebugBuffers();
}
